/*******************************************************************************
 * Copyright (c) 2006 CEA List.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *     CEA List - initial API and implementation
 *******************************************************************************/
package com.cea.papyrus.core.commands.connection;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

import org.eclipse.draw2d.geometry.Dimension;
import org.eclipse.draw2d.geometry.Point;
import org.eclipse.draw2d.geometry.Rectangle;
import org.eclipse.gef.commands.Command;

import com.cea.papyrus.diagraminterchange2.di2.GraphConnector;
import com.cea.papyrus.diagraminterchange2.di2.GraphEdge;

/**
 * Base class for Bendpoint commands.
 * This class provide management of the anchors attached to the connection.
 * 
 * @author Dumoulin
 */
public class BendpointCommandBase extends Command {
	  
	  /**
  	 * Get the location of the source of the specified segment.
  	 * 
  	 * @param index 
  	 * @param connection 
  	 * 
  	 * @return 
  	 */
	  protected Point getSegmentSourceLocation(GraphEdge connection, int index)
	  {
		  if(index<=0)
			  return ((GraphConnector)connection.getAnchor().get(0)).getPosition().getCopy();
		  
		  return ((Point)connection.getWaypoints().get(index-1)).getCopy();
	  }
	  
	  /**
  	 * Get the location of the target of the specified segment.
  	 * 
  	 * @param index 
  	 * @param connection 
  	 * 
  	 * @return 
  	 */
	  protected Point getSegmentTargetLocation(GraphEdge connection, int index)
	  {
		  List waypoints = connection.getWaypoints();
		  
		  // Check if last segment
		  if(index >= waypoints.size() ) 
			  return ((GraphConnector)connection.getAnchor().get(1)).getPosition().getCopy();
		  
		  return ((Point)waypoints.get(index)).getCopy();
	  }
	  
	  /**
  	 * Compute the middle point.
  	 * 
  	 * @param p1 
  	 * @param p2 
  	 * 
  	 * @return 
  	 */
	  protected Point middlePoint(Point p1, Point p2)
	  {
		  Dimension d = p2.getDifference(p1);
		  return new Point(p1.x + d.width / 2, p1.y + d.height / 2);
	  }
	  
	  
	  /**
  	 * Save the anchor positions of a segment.
  	 * 
  	 * @author dumoulin
  	 */
	protected class SegmentAnchors 
	{
		
		/**
		 * 
		 */
		Point source;
		
		/**
		 * 
		 */
		Point target;
		
		/**
		 * 
		 */
		List<SavedAnchor> segmentAnchors;
		
		/**
		 * Create a SegmentAnchors.
		 * Save the anchors belonging to the specified segment.
		 * 
		 * @param p1 
		 * @param p2 
		 * @param anchors 
		 */
		public SegmentAnchors(Point p1, Point p2, List<GraphConnector> anchors)
		{
			source = p1;
			target = p2;
			saveAnchors(p1, p2, anchors);
		}
		
		/**
		 * Save the anchors belonging to the specified segment.
		 * 
		 * @param p1 
		 * @param p2 
		 * @param anchors 
		 */
		public void saveAnchors(Point p1, Point p2, List<GraphConnector> anchors)
		{
			segmentAnchors = new ArrayList<SavedAnchor>();
			Iterator<GraphConnector> iter = anchors.iterator();
			while(iter.hasNext())
			{
				GraphConnector anchor = iter.next();
				if( lineContainsPoint(p1, p2, anchor.getPosition() ) )
				{ // Anchor is on the segment
					segmentAnchors.add(new SavedAnchor(anchor, p1.getDistance2(anchor.getPosition()), p1.getDistance2(p2) ) );
				}
			}
		}
		
		/**
		 * Restore the saved anchors.
		 */
		public void restoreAnchors()
		{
			Iterator<SavedAnchor> iter = segmentAnchors.iterator();
			while(iter.hasNext())
			{
				SavedAnchor savedAnchor = iter.next();
				savedAnchor.restore();
			}
					
		}
		
		/**
		 * Move the source end to the new position.
		 * Recalculate position of anchors on the segment.
		 * 
		 * @param p1 
		 * @param p2 
		 * @param newPos 		 */
		public void moveToSegment(Point p1, Point p2)
		{
			Iterator<SavedAnchor> iter = segmentAnchors.iterator();
			while(iter.hasNext())
			{
				SavedAnchor savedAnchor = iter.next();
				savedAnchor.moveToSegment(p1, p2);
			}
			
		}
		
		/**
		 * Move the source end to the new position.
		 * Recalculate position of anchors on the segment.
		 * 
		 * @param newPos 
		 */
		public void moveSourceEnd(Point newPos ) {
			moveToSegment(newPos, target);
		}
		
		/**
		 * Move the source end to the new position.
		 * Recalculate position of anchors on the segment.
		 * 
		 * @param newPos 
		 */
		public void moveTargetEnd(Point newPos)
		{
			moveToSegment(source, newPos);			
		}
		
		/**
		 * Does the specified line contains the specified point ?.
		 * 
		 * @param p1 
		 * @param p 
		 * @param p2 
		 * 
		 * @return 
		 */
		private boolean lineContainsPoint(Point p1, Point p2, Point p) {
			int tolerance = 7;
			Rectangle rect = Rectangle.SINGLETON;
			rect.setSize(0, 0);
			rect.setLocation(p1.x, p1.y);
			rect.union(p2.x, p2.y);
			rect.expand(tolerance, tolerance);
			if (!rect.contains(p.x, p.y))
				return false;

			int v1x, v1y, v2x, v2y;
			int numerator, denominator;
			double result = 0.0;

			if (p1.x != p2.x && p1.y != p2.y) {
				
				v1x = p2.x - p1.x;
				v1y = p2.y - p1.y;
				v2x = p.x - p1.x;
				v2y = p.y - p1.y;
				
				numerator = v2x * v1y - v1x * v2y;
				denominator = v1x * v1x + v1y * v1y;

				result = ((numerator << 10) / denominator * numerator) >> 10;
			}
			
			// if it is the same point, and it passes the bounding box test,
			// the result is always true.
			return result <= tolerance * tolerance;
		}

		/**
		 * Class used to store an anchor state.
		 * 
		 * @author dumoulin
		 */
      protected class SavedAnchor {
    	  
  	    /**
  	     * 
  	     */
  	    GraphConnector anchor;
    	  
  	    /**
  	     * 
  	     */
  	    Point oldPosition;
    	  
  	    /**
  	     * 
  	     */
  	    double distance2;
    	  
  	    /**
  	     * 
  	     */
  	    double oldSegmentSize;
    	  
    	  /**
  	     * 
  	     * 
  	     * @param oldSegmentSize 
  	     * @param distance2 
  	     * @param anchor 
  	     */
  	    protected SavedAnchor(GraphConnector anchor, double distance2, double oldSegmentSize)
    	  {
    		this.anchor = anchor;
    		oldPosition = anchor.getPosition();
    		this.distance2 = distance2;
    		this.oldSegmentSize = oldSegmentSize;
    	  }
    	  
    	  /**
  	     * 
  	     */
  	    protected void restore()
    	  {
    		  anchor.setPosition(oldPosition);
    	  }
    	  
    	  /**
  	     * 
  	     * 
  	     * @param p1 
  	     * @param p2 
  	     */
  	    protected void moveToSegment(Point p1, Point p2)
    	  {
    		  
    		  double D2 = p1.getDistance2(p2);
    		  double d2 = (distance2/oldSegmentSize)*D2;
    		  
    		  Point p = new Point(p2.x-p1.x, p2.y-p1.y);
    		  
       		  double x = Math.sqrt(d2/D2)*p.x;
       		  double y = Math.sqrt(d2/D2)*p.y;
       		  
       		  Point newPos = new Point(p1.x+x, p1.y+y);
       		  anchor.setPosition(newPos);
       		  
    		  
    	  }

		/**
		 * 
		 * 
		 * @return the distance2
		 */
		public double getDistance2() {
			return distance2;
		}
    	  
      }
	}
	}
